package com.jiujichaoshi.oauth.server.security.sms;

import com.jiujichaoshi.oauth.server.security.DefaultVariableName;
import com.jiujichaoshi.oauth.server.security.ExtendAuthenticationToken;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import org.springframework.security.core.userdetails.UserDetailsService;

import java.util.Objects;

/**
 * Description: grant_type为sms的认证信息进行认证授权
 *
 * @author YangLong [410357434@163.com]
 * @version V1.0
 * @date 2021/2/4
 */
@Slf4j
@Data
public class SmsAuthenticationProvider implements AuthenticationProvider {
    private SmsCaptchaFindService smsCaptchaFindService;
    private UserDetailsService userDetailsService;
    private UserDetailsChecker preAuthenticationChecks = new DefaultPreAuthenticationChecks();
    private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

    public SmsAuthenticationProvider(SmsCaptchaFindService smsCaptchaFindService, UserDetailsService userDetailsService) {
        this.smsCaptchaFindService = smsCaptchaFindService;
        this.userDetailsService = userDetailsService;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        ExtendAuthenticationToken token = (ExtendAuthenticationToken) authentication;
        log.info("login Authentication info:{}",authentication.toString());
        if(token.verify()) {
            //判断是否支持认证
            if (Objects.equals(DefaultVariableName.SMS_GRANT_TYPE, token.getTokenType())) {
                String phone=token.getPrincipal().toString();
                //如果要扩展不同的登录方式，此处先在线程里设置共享变量，放入查询依据是什么，再在UserDetailsService中取出，然后进行不同的实现
                UserDetails user = userDetailsService.loadUserByUsername(phone);
                preAuthenticationChecks.check(user);
                //获取已经缓存的验证码
                String cachedCode = smsCaptchaFindService.getCaptchaByPhone(phone);
                //获取前天传递过来的验证码
                String code = token.getCredentials().toString();
                if (Objects.equals(code, cachedCode)) {
                    //构建已认证token
                    UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(user.getUsername(),
                            authentication.getCredentials(), user.getAuthorities());
                    result.setDetails(authentication.getDetails());
                    return result;
                }
                log.debug("sms captcha is not equal that in the server side!");
                throw new SmsCaptchaException("captcha is bad");
            }
        }
        log.debug("can't receive the sms loin request parameter!");
        throw new SmsBadParamException("Principal and Credentials is needed");
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return ExtendAuthenticationToken.class.isAssignableFrom(authentication);
    }

    private class DefaultPreAuthenticationChecks implements UserDetailsChecker {
        @Override
        public void check(UserDetails user) {
            if (!user.isAccountNonLocked()) {
                log.debug("User account is locked");

                throw new LockedException(messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.locked",
                        "User account is locked"));
            }

            if (!user.isEnabled()) {
                log.debug("User account is disabled");

                throw new DisabledException(messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.disabled",
                        "User is disabled"));
            }

            if (!user.isAccountNonExpired()) {
                log.debug("User account is expired");

                throw new AccountExpiredException(messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.expired",
                        "User account has expired"));
            }
        }
    }

    private class DefaultPostAuthenticationChecks implements UserDetailsChecker {
        @Override
        public void check(UserDetails user) {
            if (!user.isCredentialsNonExpired()) {
                log.debug("User account credentials have expired");

                throw new CredentialsExpiredException(messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.credentialsExpired",
                        "User credentials have expired"));
            }
        }
    }
}
